import subprocess
import re
from ipaddress import IPv4Address, IPv4Network
import random
import argparse
from concurrent.futures import ThreadPoolExecutor
from threading import Lock

from .traffic_configuration import TrafficConfiguration

tracert_percentage = 5
localhost_networks = [IPv4Network('10.0.0.0/8'), IPv4Network('172.16.0.0/12'), IPv4Network('192.168.0.0/16')]

# TCPDUMP consts
DUMP_PARSER = r'..\IPAlgorithm\tcpdump.jar'
DUMP_PARSER_ARGS = 'ttl sip sp dip dp'
SIP_INDEX = 1
TTL_INDEX = 0


def tracert(hostname):
    print ('tracerting {0}'.format(hostname))
    return subprocess.check_output('tracert {0}'.format(hostname)).decode('utf-8')

def unix_process_traceroute(hostname, unix_process_cmd_line):
    print ('tracerouting {0} through {1}'.format(hostname, unix_process_cmd_line))
    p = subprocess.Popen(unix_process_cmd_line, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = p.communicate('traceroute {0}\nexit\n'.format(hostname).encode())
    return stdout.decode()

def number_of_hops_in_tracert(tracert_result):
    return len(re.findall('(\d*)\s*(?:\d*\s*ms|\*)\s*.*', tracert_result))

def estimate_initial_ttl_value(hostname, current_ttl_value):
    tracert_result = tracert(hostname)
    tracert_len = number_of_hops_in_tracert(tracert_result)
    # -1 Since the tracert includes the destination, which doesn't substracts 1.
    return tracert_len + current_ttl_value - 1

def is_localhost_address(ip):
    return len(list(filter(lambda network: ip in network, localhost_networks))) > 0

# Seen IPs
seen_ips = []

def default_predicate(ttl, sip, sp, dip, dp):
    sip = IPv4Address(int(sip))

    # Filter outband traffic
    if is_localhost_address(sip):
        return False

    random_value = random.uniform(0, 100)
    if random_value < tracert_percentage:
        if sip in seen_ips:
            return False
        seen_ips.append(sip)
        return True

    return False

def estimate_ttl_thread(ttl_histogram, l, ip, ttl):
    initial_ttl_value = estimate_initial_ttl_value(ip, ttl)
    with l:
        if initial_ttl_value in ttl_histogram:
            ttl_histogram[initial_ttl_value] += 1
        else:
            ttl_histogram[initial_ttl_value] = 1


def number_of_hops_to_hostname(hostname):
    return number_of_hops_in_tracert(tracert(hostname))

def tracert_path(hostname):
    return re.findall('(\d*)\s*(?:\d*\s*ms|\*)\s*.*\[(\d+\.\d+\.\d+\.\d+)', tracert(hostname))

def unix_process_tracert_path(hostname, process_cmd_line = 'plink.exe tamirc@csd.technion.ac.il -pw smfGn112'):
    return re.findall('(\d+)\s+.*\((\d+\.\d+\.\d+\.\d+)\)\s+', unix_process_traceroute(hostname, process_cmd_line))

def estimate_initial_ttl_values_from_pcap(pcap_file, predicate = default_predicate):
    ttl_histogram = {}
    l = Lock()
    p = subprocess.Popen('java -jar {0} {1} {2}'.format(DUMP_PARSER, pcap_file, DUMP_PARSER_ARGS), stdout=subprocess.PIPE)
    with ThreadPoolExecutor(max_workers=TrafficConfiguration.NUMBER_OF_THREADS) as executor:
        for line in p.stdout.readlines():
            packet_data = line.decode("utf-8").split()
            if predicate(*packet_data):
                ip = IPv4Address(int(packet_data[SIP_INDEX])).exploded
                ttl = abs(int(packet_data[TTL_INDEX]))
                executor.submit(estimate_ttl_thread, ttl_histogram, l, ip, ttl)
    return sorted(ttl_histogram.items())

if (__name__ == "__main__"):
    parser = argparse.ArgumentParser()
    parser.add_argument("pcap_file",)
    parser.add_argument('-i', '--histogram_file', default='ttl_histogram.txt')
    args = parser.parse_args()
    hist = estimate_initial_ttl_values_from_pcap(args.pcap_file)

    number_of_packets = sum(dict(hist).values())
    with open(args.histogram_file,'w') as hist_file:
        for key, value in hist:
            hist_file.write('%d: %d (%.2f%%)\r\n'%(key, value, 100* value / number_of_packets,))
        hist_file.write('Total of {0} ips tracerouted'.format(number_of_packets))